/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Benoit Saint-Maxent
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.controller.command {
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import marcel.debug.*;
	import marcel.events.AsyncRequestEvent;


	/**
	 * @eventType marcel.events.AsyncRequestEvent.PROGRESS
	 */
	[Event(name = "progress", type = "marcel.events.AsyncRequestEvent")]
	
	/**
	 * @eventType marcel.events.AsyncRequestEvent.FAULT
	 */
	[Event(name = "fault", type = "marcel.events.AsyncRequestEvent")]
	
	/**
	 * @eventType marcel.events.AsyncRequestEvent.RESULT
	 */
	[Event(name = "result", type = "marcel.events.AsyncRequestEvent")]
	
	
	/**
	 * AsyncMacroCommand implements IAsyncCommand and extends MacroCommand
	 * The AsyncRequestEvent dispatched by the last called IAsyncCommand is passed
	 * to the execute method of the next IAsyncCommand
	 *
	 * @author Benoit Saint-Maxent
	 */
	public class AsyncMacroCommand extends MacroCommand implements IAsyncMacroCommand {

		
		
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		
		/**
		 * @private
		 */
		protected var _ed : EventDispatcher;
		
		/**
		 * @private
		 */
		protected var _nWeight : uint;
		
		/**
		 * @private
		 */
		protected var _nMaxAttempts : uint;
		
		/**
		 * @private
		 */
		protected var _nCurrentAttempt : uint;
		
		/**
		 * @private
		 */
		protected var _bIsProcessing : Boolean;
		
		/**
		 * @private
		 */
		protected var _nCurrentStep : uint;
		
		/**
		 * @private
		 * (0 --> 1)
		 */
		protected var _nPercentProgress : Number;
		
		/**
		 * @private
		 */
		protected var _passedInEvent : Event;
		
		/**
		 * @private
		 */
		protected var _passedInEventOnStart : Event;

		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		
		/**
		 * Constructs a new AsyncCommand instance
		 * @param	numMaxAttempts	The maximum number of attempts to run the query, in case of failure
		 * @param	weight			The weight of the AsyncCommand (Useful only if the current AsyncCommand is part of a IMacroCommand stack)
		 */
		public function AsyncMacroCommand(numMaxAttempts : uint = 1, weight : uint = 1) {
			_ed = new EventDispatcher(this);
			this.numMaxAttempts = numMaxAttempts;
			this.weight = weight;
		}

		
		
		//--------------------------------------------------------------------------
		//
		//  Public methods
		//
		//--------------------------------------------------------------------------

		override public function execute(event : Event = null) : void {
			if(_bIsProcessing) return;
			debugi(this + '.execute( ' + (event ? event : '') + ' )');
			if(numSubCommands == 0) {
				debugw(this + ':: No sub-command registered');
				return;
			}
			_passedInEventOnStart = event;
			_nCurrentAttempt = 0;
			_bIsProcessing = true;
			_nPercentProgress = 0;
			notifyProgress();
			_run();
		}

		/**
		 * @inheritDoc
		 */
		public function notifyProgress() : void {
			dispatchEvent(new AsyncRequestEvent(AsyncRequestEvent.PROGRESS, percentProgress));
		}
		
		/**
		 * @inheritDoc
		 */
		public function notifyFault(error : Error = null) : void {
			_bIsProcessing = false;
			dispatchEvent(new AsyncRequestEvent(AsyncRequestEvent.FAULT, 0, null, error));
		}
		
		/**
		 * @inheritDoc
		 */
		public function notifyResult(result : * = null) : void {
			notifyProgress();
			_bIsProcessing = false;
			dispatchEvent(new AsyncRequestEvent(AsyncRequestEvent.RESULT, 1, result));
		}
		
		/**
		 * @inheritDoc
		 */
		public function get weight() : uint {
			return _nWeight;
		}
		
		/**
		 * @inheritDoc
		 */
		public function set weight(n : uint) : void {
			_nWeight = Math.max(1, n);
		}
		
		/**
		 * @inheritDoc
		 */
		public function get numMaxAttempts() : uint {
			return _nMaxAttempts;
		}

		/**
		 * @inheritDoc
		 */
		public function set numMaxAttempts(n : uint) : void {
			_nMaxAttempts = Math.max(1, n);
		}
		
		/**
		 * @inheritDoc
		 * Should be overriden to return the real progress of the current process
		 */
		public function get percentProgress() : Number {
			return _nPercentProgress;
		}

		/**
		 * @inheritDoc
		 */
		public function get isProcessing() : Boolean {
			return _bIsProcessing;
		}

		/**
		 * @inheritDoc
		 */
		public function get currentProcessingCommand() : ICommand {
			if(!_bIsProcessing) return null;
			return _aCommands[_nCurrentStep];
		}

		/**
		 * @inheritDoc
		 */
		public function kill() : void {
			for each (var c : ICommand in _aCommands) {
				if(!(c is AsyncCommand)) continue;
				var ac : IAsyncCommand = c as AsyncCommand;
				_removeCommandListening(ac);
				ac.kill();
			}
		}

		
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		
		/**
		 * @private
		 */
		protected function _run() : void {
			_nCurrentStep = 0;
			_passedInEvent = _passedInEventOnStart;
			_runNextCommand();
		}

		/**
		 * @private
		 */
		protected function _runNextCommand() : void {
			var c : ICommand = _aCommands[_nCurrentStep];
			if(c is IAsyncCommand) {
				var ac : IAsyncCommand = c as IAsyncCommand;
				ac.addEventListener(AsyncRequestEvent.FAULT, _onSubFault);
				ac.addEventListener(AsyncRequestEvent.RESULT, _onSubResult);
				ac.addEventListener(AsyncRequestEvent.PROGRESS, _onSubProgress);
			}
			c.execute(_passedInEvent);
			if(!(c is IAsyncCommand)) _onSubResult();
		}

		/**
		 * @private
		 */
		protected function _onSubProgress(event : AsyncRequestEvent) : void {
			var nTotW : uint = 0;
			var nProcessedW : uint = 0;
			var c : ICommand;
			var ac : IAsyncCommand;
			for (var i : int = 0; i < numSubCommands; i++) {
				c = _aCommands[i];
				ac = c is IAsyncCommand ? c as IAsyncCommand : null;
				nTotW += ac ? ac.weight : 0;
				nProcessedW += i < _nCurrentStep && ac ? ac.weight : 0;
			}
			c = currentProcessingCommand;
			ac = c is IAsyncCommand ? c as IAsyncCommand : null;
			nProcessedW += ac ? ac.percentProgress * ac.weight : 0;
			_nPercentProgress = Math.max(_nPercentProgress, nProcessedW / nTotW);
			notifyProgress();
		}

		/**
		 * @private
		 */
		protected function _onSubFault(event : AsyncRequestEvent) : void {
			_nCurrentAttempt++;
			if(_nCurrentAttempt >= _nMaxAttempts) {
				_bIsProcessing = false;
				notifyFault(event.error);
				return;
			}
			_run();
		}

		/**
		 * @private
		 */
		protected function _onSubResult(event : AsyncRequestEvent = null) : void {
			notifyProgress();
			if(event) _passedInEvent = event;
			_nCurrentStep++;
			if(_nCurrentStep >= numSubCommands) {
				_bIsProcessing = false;
				notifyResult(_passedInEvent is AsyncRequestEvent ? (_passedInEvent as AsyncRequestEvent).result : null);
				return;
			}
			_runNextCommand();
		}

		/**
		 * @private
		 */
		protected function _removeCommandListening(c : IAsyncCommand) : void {
			c.removeEventListener(AsyncRequestEvent.FAULT, _onSubFault);
			c.removeEventListener(AsyncRequestEvent.RESULT, _onSubResult);
			c.removeEventListener(AsyncRequestEvent.PROGRESS, _onSubProgress);
		}

		
		
		
		
		//--------------------------------------------------------------------------
		//
		//  Event
		//
		//--------------------------------------------------------------------------
		
		public function dispatchEvent(event : Event) : Boolean {
			return _ed.dispatchEvent(event);
		}
		
		public function hasEventListener(type : String) : Boolean {
			return _ed.hasEventListener(type);
		}
		
		public function willTrigger(type : String) : Boolean {
			return _ed.willTrigger(type);
		}
		
		public function removeEventListener(type : String, listener : Function, useCapture : Boolean = false) : void {
			_ed.removeEventListener(type, listener, useCapture);
		}
		
		public function addEventListener(type : String, listener : Function, useCapture : Boolean = false, priority : int = 0, useWeakReference : Boolean = false) : void {
			_ed.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
	}
}
